﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using LinFu.IoC.Configuration;
using LinFu.IoC.Configuration.Interfaces;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.DynamicInvoker.Implementation
{
    [Implements(typeof(IMemberFinder))]
    public class MemberFinder :IMemberFinder,IInitialize
    {
        private IMethodFinder<MethodBase> _methodFinder;
        private IParameterTypeResolver _parameterTypeResolver;
        private IHashBuilder _hashBuilder;
        private IMethodConstructor _methodConstructor;


        private static readonly IDictionary<int, MemberInfo> _memberInfoCache = new Dictionary<int, MemberInfo>();

        public MemberInfo GetMember(Type type, string memberName, object[] arguments)
        {
            if (arguments == null)
                arguments = new object[] { };

            int hashCode = _hashBuilder.CreateHashCode(type, memberName, _parameterTypeResolver.GetTypes(arguments));
            if (!_memberInfoCache.ContainsKey(hashCode))
            {
                
                //Find all members that matches the member name.
                var members =
                    type.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public |
                                    BindingFlags.Static).Where(m => m.Name == memberName);

                //No member is found
                if (members.Count() == 0)
                    return null;
                                
                var methods = members.Where(m => typeof(MethodBase).IsAssignableFrom(m.GetType())).Cast<MethodBase>();
                
                if (methods.Count() > 0)
                {
                    //Create a list to hold all the target methods
                    var targetMethods = new List<MethodBase>();
                
                    //Get a list of the parameter types
                    var parameterTypes = _parameterTypeResolver.GetTypes(arguments);

                    //Get a list of the methods that are generic method definitions.
                    var genericMethods =
                        methods.Where(
                            m => typeof (MethodInfo).IsAssignableFrom(m.GetType()) && m.IsGenericMethodDefinition).Cast<MethodInfo>();
                    
                    //Add the constructed generic methods
                    targetMethods.AddRange(_methodConstructor.MakeConstructedMethods(genericMethods.ToArray(),parameterTypes).Cast<MethodBase>());
                    
                    //Add the non-generic methods
                    targetMethods.AddRange(methods.Where(m => !m.IsGenericMethodDefinition));
                    
                    //Find the method that best matches the list of arguments
                    _memberInfoCache.Add(hashCode, _methodFinder.GetBestMatch(methods, new MethodFinderContext(arguments)));
                }
                    
                else
                    _memberInfoCache.Add(hashCode, members.FirstOrDefault());

            }

            return _memberInfoCache[hashCode];
        }

       


        public void Initialize(IServiceContainer source)
        {
            _methodFinder = source.GetService<IMethodFinder<MethodBase>>();
            _hashBuilder = source.GetService<IHashBuilder>();
            _parameterTypeResolver = source.GetService<IParameterTypeResolver>();
            _methodConstructor = source.GetService<IMethodConstructor>();
        }
    }
}
